package com.sduept.zj103;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ssssssss.magicapi.Const;
import org.ssssssss.magicapi.core.service.MagicAPIService;
import org.ssssssss.magicapi.core.service.impl.DefaultMagicAPIService;
import org.tio.client.intf.TioClientHandler;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.core.TioConfig;
import org.tio.core.exception.TioDecodeException;
import org.tio.core.intf.Packet;

import com.google.common.io.LittleEndianDataInputStream;
import com.sduept.tool.CrcUtil;
import com.sduept.tool.PrintTool;
import com.sduept.tool.TimeTool;
import com.sduept.zj103.model.Apci;
import com.sduept.zj103.model.Asdu;
import com.sduept.zj103.model.FileData;
import com.sduept.zj103.model.FileInfo;
import com.sduept.zj103.model.FileListInfo;
import com.sduept.zj103.model.FrameType;
import com.sduept.zj103.model.InsideMessage;
import com.sduept.zj103.model.Telecommand;
import com.sduept.zj103.model.UFrameMark;
import com.sduept.zj103.model.Zj103Protocol;

import java.io.ByteArrayInputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Zj103ClientHandler implements TioClientHandler {    
	private static Logger logger = LoggerFactory.getLogger(Zj103ClientHandler.class);
    /**
     * 解码：把接收到的ByteBuffer，解码成应用可以识别的业务消息包
     * 总的消息结构：消息头 + 消息体
     * 消息头结构：    4个字节，存储消息体的长度
     * 消息体结构：   对象的json串的byte[]
     */

    @Override
    public Zj103Protocol decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws TioDecodeException {
        if(readableLength < Apci.APCILEN) {
            return null;
        }

        buffer.order(ByteOrder.LITTLE_ENDIAN);
        int startHeader = buffer.get() & 0xFF;
        if(startHeader != Apci.START) {
            Tio.close(channelContext, "开始帧头不对");
            logger.error("开始帧头不对");
            return null;
        }

        int apduLen = buffer.getShort();
        if(readableLength/*可读字节数*/  - 3/*已读字节数*/ < apduLen) {
            return null;
        }
//        logger.info("recv: " + PrintTool.bytes2hex(buffer.array(), position, apduLen + 3));

        int controlByte1 = buffer.get() & 0xFF;
        int controlByte2 = buffer.get() & 0xFF;
        Zj103Protocol zj103Protocol = Zj103Client.getInstance().getClients().get(channelContext.getServerNode().getIp());
        zj103Protocol.inApdu.apci.asduLen = apduLen - Apci.CONTROLLEN;
        zj103Protocol.inApdu.apci.sendNo = (controlByte2 << 8) | controlByte1;
        zj103Protocol.inApdu.apci.recvNo = buffer.getShort();

        if((controlByte1 & 0x01) == 0) { //I frame
            zj103Protocol.inApdu.apci.frameType = FrameType.I;
            zj103Protocol.inApdu.asdu.type = buffer.get() & 0xFF;
            zj103Protocol.inApdu.asdu.vsq = buffer.get() & 0xFF;
            zj103Protocol.inApdu.asdu.cot = buffer.get() & 0xFF;
            zj103Protocol.inApdu.asdu.asduAddr = buffer.getShort();
            zj103Protocol.inApdu.asdu.fun = buffer.get() & 0xFF;
            zj103Protocol.inApdu.asdu.inf = buffer.get() & 0xFF;
            zj103Protocol.inApdu.asdu.element = new byte[zj103Protocol.inApdu.apci.asduLen - Asdu.ASDUHEADERLEN];
            buffer.get(zj103Protocol.inApdu.asdu.element);
            int sendNo = zj103Protocol.inApdu.apci.sendNo >> 1; //服务端已发送的I帧数量
            int recvNo = zj103Protocol.inApdu.apci.recvNo >> 1; //服务端已接收的I帧数量
            zj103Protocol.recvIFrameNo++;
            if(zj103Protocol.recvIFrameNo % 5 == 0) { //每收到5个I帧，发送一个S确认帧
                zj103Protocol.outApdu.apci.encodeSFrame();
                Tio.send(zj103Protocol.clientChannelContext, zj103Protocol);
            }
            if(zj103Protocol.sendIFrameNo - recvNo > 15) {
//                logger.info("通过判断I帧，未被DTE认可的数量达到上限");
                logger.info("client_sendno={},client_recvno={},server_sendno={},server_recvno={}",
                        zj103Protocol.sendIFrameNo, zj103Protocol.recvIFrameNo,
                        sendNo, recvNo);
            }

        } else if((controlByte1 & 0x03) == 1) { //s frame
            zj103Protocol.inApdu.apci.frameType = FrameType.S;
            int recvNo = zj103Protocol.inApdu.apci.recvNo >> 1; //服务端已接收的I帧数量
            if(zj103Protocol.sendIFrameNo - recvNo > 15) {
                logger.info("通过判断S帧，未被DTE认可的数量达到上限");
                logger.info("client_sendno={},server_recvno={}",
                        zj103Protocol.sendIFrameNo, recvNo);
            }

        } else if((controlByte1 & 0x03) == 3) { //u frame
            zj103Protocol.inApdu.apci.frameType = FrameType.U;
            if(zj103Protocol.inApdu.apci.sendNo == 0x0B) {
                zj103Protocol.inApdu.apci.uFrameMark = UFrameMark.STARTDT;
            } else if(zj103Protocol.inApdu.apci.sendNo == 0x23) {
                zj103Protocol.inApdu.apci.uFrameMark = UFrameMark.STOPDT;
            } else if(zj103Protocol.inApdu.apci.sendNo == 0x83) {
                zj103Protocol.inApdu.apci.uFrameMark = UFrameMark.TESTFR;
            } else if(zj103Protocol.inApdu.apci.sendNo == 0x43) { //对方请求的测试帧
                zj103Protocol.outApdu.apci.encodeUFrameResponse(UFrameMark.TESTFR);
                Tio.send(zj103Protocol.clientChannelContext, zj103Protocol);
            } else if(zj103Protocol.inApdu.apci.sendNo == 0x13)  {
                logger.error("收到STOPDTU帧，不应该发生");
                zj103Protocol.outApdu.apci.encodeUFrameResponse(UFrameMark.STOPDT);
                Tio.send(zj103Protocol.clientChannelContext, zj103Protocol);
            } else if(zj103Protocol.inApdu.apci.sendNo == 0x07) {
                logger.error("收到STARTDT帧，不应该发生");
                zj103Protocol.outApdu.apci.encodeUFrameResponse(UFrameMark.STARTDT);
                Tio.send(zj103Protocol.clientChannelContext, zj103Protocol);
            } else {
                logger.info("没处理的U帧");
            }

        } else {
            Tio.close(channelContext, "帧格式不对，重新连接");
            logger.info("帧格式不对，重新连接");
            return null;
        }

        return zj103Protocol;
    }
    /**
     * 编码：把业务消息包编码为可以发送的ByteBuffer
     * 总的消息结构：消息头 + 消息体
     * 消息头结构：    4个字节，存储消息体的长度
     * 消息体结构：   对象的json串的byte[]
     */
    @Override
    public ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext) {
        Zj103Protocol zj103Protocol = (Zj103Protocol) packet;
        ByteBuffer byteBuffer = ByteBuffer.allocate(zj103Protocol.outApdu.apci.getApduLen() + 3/*1个启动字符 + 2个apdu_len*/);
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        byteBuffer.put((byte) Apci.START);
        byteBuffer.putShort((short) zj103Protocol.outApdu.apci.getApduLen());

        switch (zj103Protocol.outApdu.apci.frameType) {
            case I:
                encodeIFrame(zj103Protocol, byteBuffer);
                zj103Protocol.sendIFrameNo++; //发送完I帧计数，第一帧序号为0
                break;
            case S:
                encodeSFrame(zj103Protocol, byteBuffer);
                break;
            case U:
                encodeUFrame(zj103Protocol, byteBuffer);
                break;
            case X:
                logger.error("无效帧");
                break;
            default:
                break;
        }

        logger.info("send: " + PrintTool.bytes2hex(byteBuffer.array()));

        return byteBuffer;
    }
    /**
     * 处理消息
     */
    @Override
    public void handler(Packet packet, ChannelContext channelContext) throws Exception {
        Zj103Protocol zj103Protocol = (Zj103Protocol)packet;
        if(zj103Protocol.inApdu.apci.frameType != FrameType.I) {
            return;
        }

        FileData fileData = null;
        switch (zj103Protocol.inApdu.asdu.type) {
            case Asdu.TYPE_COMMSTATE:
                decodeCommStateFrame(zj103Protocol);
                break;
            case Asdu.TYPE_DEVTIME:
                decodeDevTime(zj103Protocol);
                break;
            case Asdu.TYPE_LBFILE:
                fileData = decodeFileFrame(zj103Protocol, channelContext);
                if(fileData != null) {
                    if (fileData.finishFlag) {

                    }
                }
                break;
            case Asdu.TYPE_FILELIST:
                decodeFilelistFrame(zj103Protocol, channelContext);
                break;
            case Asdu.TYPE_LBRECORD_FILE:
                fileData = decodeFileFrame(zj103Protocol, channelContext);
                if(fileData != null) {
                    if (fileData.finishFlag) {

                    }
                }
                break;
            default:
                break;
        }
    }
    /**
     * 此方法如果返回null，框架层面则不会发心跳；如果返回非null，框架层面会定时发本方法返回的消息包
     */


    private boolean decodeDevTime(Zj103Protocol zj103Protocol) {
        if(zj103Protocol.inApdu.getElemetLen() != zj103Protocol.inApdu.asdu.element.length) {
            logger.error("装置时间帧错误，asdu长度不匹配，无法解析");
            return false;
        }

        try {
            LittleEndianDataInputStream littleEndianDataInputStream = new LittleEndianDataInputStream(new ByteArrayInputStream(zj103Protocol.inApdu.asdu.element));
            int rii = littleEndianDataInputStream.readUnsignedByte();
            byte[] devTimeBytes = new byte[7];
            littleEndianDataInputStream.read(devTimeBytes);
            Date devTime = TimeTool.getDateByCp56(devTimeBytes);
            zj103Protocol.devTimeMap.put(rii, devTime);

        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    private boolean decodeCommStateFrame(Zj103Protocol zj103Protocol) {
        if(zj103Protocol.inApdu.getElemetLen() != zj103Protocol.inApdu.asdu.element.length) {
            logger.error("通信状态帧错误，asdu长度不匹配，无法解析");
            return false;
        }

        try {
            if(zj103Protocol.inApdu.asdu.fun == Asdu.FUN_COMM) {
                LittleEndianDataInputStream littleEndianDataInputStream = new LittleEndianDataInputStream(new ByteArrayInputStream(zj103Protocol.inApdu.asdu.element));
                int dpi = littleEndianDataInputStream.readUnsignedByte();
                byte[] occurTimeBytes = new byte[7];
                littleEndianDataInputStream.read(occurTimeBytes);
                Date occurTime = TimeTool.getDateByCp56(occurTimeBytes);
                int sin = littleEndianDataInputStream.readUnsignedByte();
                Telecommand telecommand = new Telecommand();
                telecommand.inf = zj103Protocol.inApdu.asdu.inf;
                telecommand.dpi = dpi;
                telecommand.pickTime = occurTime;
                telecommand.sin = sin;
                zj103Protocol.telecommandMap.put(telecommand.inf, telecommand);
            }

        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    private FileData decodeFileFrame(Zj103Protocol zj103Protocol, ChannelContext channelContext) {
        FileData fileData = null;
        if(zj103Protocol.inApdu.getElemetLen() != zj103Protocol.inApdu.asdu.element.length) {
            logger.error("录波文件帧错误，asdu长度不匹配，无法解析");
            return null;
        }

        try {
            LittleEndianDataInputStream littleEndianDataInputStream = new LittleEndianDataInputStream(new ByteArrayInputStream(zj103Protocol.inApdu.asdu.element));
            int rii = littleEndianDataInputStream.readUnsignedByte();

            int finishFlag = littleEndianDataInputStream.readUnsignedByte();
            int beginPos = littleEndianDataInputStream.readInt();
            if(zj103Protocol.fileDataMap.containsKey(rii)) {
                fileData = zj103Protocol.fileDataMap.get(rii);
            } else {
                fileData = new FileData();
                zj103Protocol.fileDataMap.put(rii, fileData);
                fileData.rii = rii;
                fileData.cot = zj103Protocol.inApdu.asdu.cot;
                fileData.data = new byte[FileData.DEFAULT_SIZE];
            }
            littleEndianDataInputStream.read(fileData.data, beginPos, zj103Protocol.inApdu.getElemetLen() - 6);
            fileData.transCountMap.put(new Integer(beginPos), beginPos + zj103Protocol.inApdu.getElemetLen() - 6);

            if (finishFlag == 0) { //最后的帧
                fileData.fileName = new String(fileData.data, 0, 40, "utf-8");
                fileData.fileName = fileData.fileName.trim();

                //检查文件完整性，补招不完整的帧，正常情况比如(0,10),(10,20),(20,30)
                List<Integer> keys = new ArrayList<>(fileData.transCountMap.keySet());
                keys.sort(new Comparator<Integer>() {
                    @Override
                    public int compare(Integer o1, Integer o2) {
                        if(o1.intValue() < o2.intValue()) {
                            return -1;
                        } else if(o1.intValue() > o2.intValue()){
                            return 1;
                        } else {
                            return 0;
                        }
                    }
                });
                boolean isCompleteFlag = true;
                for(int i = 0; i < keys.size(); i++) {
                    Integer endPos = fileData.transCountMap.get(keys.get(i));
                    if(fileData.transCountMap.containsKey(endPos)) { //连续
                        continue;
                    }

                    if( i == keys.size() - 1) { //录波器上传的文件列表长度跟实际文件大小不一样
                        fileData.fileSize = endPos.intValue();
                        break;
                    }

                    isCompleteFlag = false;
                    //少帧，需要补招
                    if(i == keys.size() - 1) { //当前为最后一个循环，出错
                        logger.error("当前为最后一个循环，出错");
                        return null;
                    }

                    //开始位置为endPos.intValue()， 长度为下一个key - endPos
                    int callBeginPos = endPos.intValue();
                    fileData.finishFlag = false;
                    if(zj103Protocol.inApdu.asdu.type == Asdu.TYPE_LBFILE) {
                        zj103Protocol.outApdu.encodeAsdu13(zj103Protocol.inApdu.asdu.asduAddr, rii, zj103Protocol.inApdu.asdu.cot, fileData.fileName, callBeginPos);
                    } else if(zj103Protocol.inApdu.asdu.type == Asdu.TYPE_LBRECORD_FILE) {
                        zj103Protocol.outApdu.encodeAsdu113(zj103Protocol.inApdu.asdu.asduAddr, rii, fileData.fileName, callBeginPos);
                    } else {
                        logger.error("未知故障类型");
                        return null;
                    }
                    Tio.send(channelContext, zj103Protocol); //每次仅重新召唤一次
                    break;
                }

                if(isCompleteFlag) { //文件完整接收
                    fileData.finishFlag = true;
                    fileData.transCountMap.clear();
                }

            } else { //有后续帧
                fileData.finishFlag = false;
            }

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

        if(fileData.finishFlag) {
            InsideMessage insideMessage = new InsideMessage();
            insideMessage.setType(zj103Protocol.inApdu.asdu.type);
            if(zj103Protocol.inApdu.asdu.type == Asdu.TYPE_LBFILE) {
                insideMessage.setTypeDesc("录波文件");
            } else if(zj103Protocol.inApdu.asdu.type == Asdu.TYPE_LBRECORD_FILE) {
                insideMessage.setTypeDesc("录波简报文件");
            }
            insideMessage.setRii(fileData.rii);
            insideMessage.setIpAddr(channelContext.getServerNode().getIp());
            //Jboot.sendEvent("finishEvt", insideMessage);
        }

        return fileData;
    }

    private boolean decodeFilelistFrame(Zj103Protocol zj103Protocol, ChannelContext channelContext) {
        if(zj103Protocol.inApdu.getElemetLen() != zj103Protocol.inApdu.asdu.element.length) {
            logger.error("文件列表帧错误，asdu长度不匹配，无法解析");
            return false;
        }

        int finishFlag = 1;
        int rii = 0;
        try {
            LittleEndianDataInputStream littleEndianDataInputStream = new LittleEndianDataInputStream(new ByteArrayInputStream(zj103Protocol.inApdu.asdu.element));
            rii = littleEndianDataInputStream.readUnsignedByte();
            finishFlag = littleEndianDataInputStream.readUnsignedByte();
            int currentNr = littleEndianDataInputStream.readUnsignedShort();

            FileListInfo fileListInfo = null;
            if (zj103Protocol.fileListMap.containsKey(rii)) {
                fileListInfo = zj103Protocol.fileListMap.get(rii);
                littleEndianDataInputStream.skipBytes(14);
            } else { //首次添加
                fileListInfo = new FileListInfo();
                zj103Protocol.fileListMap.put(rii, fileListInfo);
                fileListInfo.rii = rii;
                fileListInfo.cot = zj103Protocol.inApdu.asdu.cot;
                byte[] beginTimeByte = new byte[7];
                byte[] endTimeByte = new byte[7];
                littleEndianDataInputStream.read(beginTimeByte);
                littleEndianDataInputStream.read(endTimeByte);
                fileListInfo.begin = TimeTool.getDateByCp56(beginTimeByte);
                fileListInfo.end = TimeTool.getDateByCp56(endTimeByte);
            }

            if (finishFlag == 0) { //最后的帧
                fileListInfo.finishFlag = true;
            } else { //有后续帧
                fileListInfo.finishFlag = false;
            }

            for(int i = 0; i < currentNr; i++) {
                FileInfo fileInfo = new FileInfo();
                fileInfo.devAddr = littleEndianDataInputStream.readUnsignedByte();
                byte[] tmpFileName = new byte[40];
                littleEndianDataInputStream.read(tmpFileName);
                fileInfo.fileName = new String(tmpFileName, Charset.forName("utf-8"));
                fileInfo.fileName = fileInfo.fileName.trim();
                byte[] cp56FaultTime = new byte[7];
                littleEndianDataInputStream.read(cp56FaultTime);
                fileInfo.faultTime = TimeTool.getDateByCp56(cp56FaultTime);
                fileInfo.fileSize = littleEndianDataInputStream.readInt();
                fileListInfo.fileInfoList.add(fileInfo);
            }

        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        if(finishFlag == 0) { //最后的帧
            InsideMessage insideMessage = new InsideMessage();
            insideMessage.setType(zj103Protocol.inApdu.asdu.type);
            insideMessage.setTypeDesc("文件列表");
            insideMessage.setRii(rii);
            insideMessage.setIpAddr(channelContext.getServerNode().getIp());
            //Jboot.sendEvent("finishEvt", insideMessage);
        }

        return true;
    }

    private void encodeSFrame(Zj103Protocol zj103Protocol, ByteBuffer byteBuffer) {
        byteBuffer.putShort((short) zj103Protocol.outApdu.apci.sendNo);
        byteBuffer.putShort((short) (zj103Protocol.recvIFrameNo << 1));
        return;
    }

    private void encodeUFrame(Zj103Protocol zj103Protocol, ByteBuffer byteBuffer) {
        byteBuffer.putShort((short) zj103Protocol.outApdu.apci.sendNo);
        byteBuffer.putShort((short) zj103Protocol.outApdu.apci.recvNo);
        return;
    }

    private void encodeIFrame(Zj103Protocol zj103Protocol, ByteBuffer byteBuffer) {
        byteBuffer.putShort((short) (zj103Protocol.sendIFrameNo << 1));
        byteBuffer.putShort((short) (zj103Protocol.recvIFrameNo << 1));
        byteBuffer.put((byte)zj103Protocol.outApdu.asdu.type);
        byteBuffer.put((byte) zj103Protocol.outApdu.asdu.vsq);
        byteBuffer.put((byte) zj103Protocol.outApdu.asdu.cot);
        byteBuffer.putShort((short) zj103Protocol.outApdu.asdu.asduAddr);
        byteBuffer.put((byte) zj103Protocol.outApdu.asdu.fun); //FUN
        byteBuffer.put((byte)zj103Protocol.outApdu.asdu.inf); //INF
        byteBuffer.put(zj103Protocol.outApdu.asdu.element);
        return;
    }
    
	@Override
	public Packet heartbeatPacket(ChannelContext channelContext) {
		// TODO Auto-generated method stub
		return null;
	}
}

